#include "videodecode.h"

#ifdef __cplusplus
extern "C" {
#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "srs_librtmp.h"

#include "log.h"
#include "errors.h"

#include "libavcodec/avcodec.h"
#include "libavutil/mem.h"
#include "libavutil/pixfmt.h"
#include "libavutil/imgutils.h"
#include "libavutil/log.h"


#define VIDEO_WIDTH		928
#define VIDEO_HEIGHT	576

#define MAX_PACKETS		30

struct rtmp_packet {
	char* data;
	int size;
	int key;
};

struct rtmp {
	srs_rtmp_t handle;
	struct rtmp_packet packets[MAX_PACKETS];
	int rindex;
	int windex;
	int nb_skips;
	
	int64_t wait_time;
};

struct video_channel {
	char rtmp_ip[64];
	char id[64];
	int channel;
	int toggle;
	
	int screen_width;
	int screen_height;
	int video_width;
	int video_height;
	int video_left;
	
	error_cb on_error_cb;
};

struct video_decode {
	AVCodecContext* ctx = NULL;
	AVFrame* yuv_frame = NULL;
	
	uint8_t* render_frame[2];
	int has_render_frame;	

	uint8_t extradata[64];
	int extradata_size;

	int has_keyframe;
	uint32_t nb_frames;
	int64_t pts;
	
	frame_available_cb on_frame_cb;
};

struct rtmp rtmp;
struct video_channel chn;
struct video_decode dec;


pthread_t thread = 0;
pthread_t thread_dec = 0;
pthread_mutex_t lock1;
pthread_mutex_t lock2;
int is_running;

static int64_t now_time()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

char* to_hexstring(uint8_t* data, int length)
{
    static char buf[255];
    char* p = buf;
    int i;
    if (length > 64)
        length = 64;
    
    for (i = 0; i < length; i++) {
        if (p != buf)
            *p++ = ' ';
        p += sprintf(p, "%02x", data[i]);
    }
    *p = 0;
    return buf;
}

static void log_callback_report(void *ptr, int level, const char *fmt, va_list vl)
{
    va_list vl2;
    char line[1024];
    static int print_prefix = 1;

    va_copy(vl2, vl);
    av_log_default_callback(ptr, level, fmt, vl);
    av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix);
    va_end(vl2);

    if (!strstr(line, "[h264"))
    	LOGI("%s",line);
}

void free_rtmppackets()
{
	int i;
	for (i = rtmp.rindex; i < rtmp.windex; i++) {
		if (rtmp.packets[i].data) {
			free(rtmp.packets[i].data);
		}
	}		
	memset(&rtmp.packets, 0, sizeof(rtmp.packets));
}

AVCodecContext* create_decoder()
{
	AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
	AVCodecContext* ctx = avcodec_alloc_context3(codec);
	if (!ctx) {
		return NULL;
	}	

	if (avcodec_open2(ctx, codec, NULL) < 0) {
		avcodec_free_context(&ctx);
		return NULL;
	}

	ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	ctx->flags2 |= CODEC_FLAG2_CHUNKS;
	dec.yuv_frame = av_frame_alloc();

	dec.render_frame[0] = (uint8_t*)malloc(VIDEO_WIDTH * VIDEO_HEIGHT * 3 / 2);
	dec.render_frame[1] = (uint8_t*)malloc(VIDEO_WIDTH * VIDEO_HEIGHT * 3 / 2);

	dec.ctx = ctx;
	return ctx;
}


void rotate_yuvframe(uint8_t* dst, uint8_t* ysrc, uint8_t* usrc, uint8_t* vsrc)
{
	int hw = VIDEO_WIDTH / 2;
	int hh = VIDEO_HEIGHT / 2;
	int hw2 = chn.video_width / 2;
	int left2 = chn.video_left / 2;
	int i, j;

	// 复制Y
	for (i = chn.video_width; i > 0; i--) {
		uint8_t* src = ysrc + i + chn.video_left;	//  + VIDEO_WIDTH * (VIDEO_HEIGHT - 1)
		for (j = chn.video_height; j > 0; j--) {
			*dst++ = *src;
			src += VIDEO_WIDTH;
		}
	}

	// 复制U,V
	uint8_t* udst = dst;
	uint8_t* vdst = dst + chn.video_width * chn.video_height / 4;
	for (i = hw2 - 1; i >= 0; i--) {
		int off = i + left2; // (hh - 1) * hw +
		uint8_t* us = usrc + off;
		uint8_t* vs = vsrc + off;
		for (j = hh; j > 0; j--) {
			*udst++ = *us;
			us += hw;

			*vdst++ = *vs;
			vs += hw;
		}
	}
}

// void yuv_rotate_270(uint8_t *des,uint8_t *ysrc,uint8_t *usrc,uint8_t *vsrc,int width,int height)
// {
    // int n = 0;
    // int hw = width / 2;
    // int hh = height / 2;
    ////copy y
    // for(int j = width; j > 0; j--)
    // {
        // for(int i = 0; i < height;i++)
        // {
            // des[n++] = ysrc[width*i + j];
        // }
    // }

    ////copy u
    // for(int j = hw; j > 0;j--)
    // {
        // for(int i = 0; i < hh;i++)
        // {
            // des[n++] = usrc[hw * i + j];
        // }
    // }

    ////copy v
    // for(int j = hw; j > 0;j--)
    // {
        // for(int i = 0; i < hh;i++)
        // {
            // des[n++] = vsrc[hw * i + j];
        // }
    // }
// }

void toggle_video_channel()
{
	char url[255];
	
	if (rtmp.wait_time > 0 && now_time() < rtmp.wait_time) {
		usleep(1000 * 50);
		return;
	}
	
	rtmp.wait_time = 0;
	if (rtmp.handle) {
		srs_rtmp_destroy(rtmp.handle);
		rtmp.handle = NULL;
	}

	sprintf(url, "rtmp://%s/game/%s%d", chn.rtmp_ip, chn.id, chn.channel);
	rtmp.handle = srs_rtmp_create(url);
	if (rtmp.handle) {
		srs_rtmp_set_timeout(rtmp.handle, 1000, 1000);
		if (srs_rtmp_handshake(rtmp.handle) != 0 || srs_rtmp_connect_app(rtmp.handle) != 0 || srs_rtmp_play_stream(rtmp.handle) != 0) {
			srs_rtmp_destroy(rtmp.handle);
			rtmp.handle = NULL;
			LOGE("ERROR: failed to connect video! IP=%s, ID=%s, CHANNEL=%d", chn.rtmp_ip, chn.id, chn.channel);
			rtmp.wait_time = now_time() + 1000;
			if (chn.on_error_cb)
				chn.on_error_cb(ERR_RTMP_CONNECT);
		}
		else {
			chn.toggle = 0;
			dec.extradata_size = 0;
		}
		dec.has_keyframe = 0;
	}
	else {
		rtmp.wait_time = now_time() + 1000;
		if (chn.on_error_cb)
			chn.on_error_cb(ERR_RTMP_OPEN);
	}
}

void* rtmp_read_thread(void* p)
{
    int size;
    char type;
    char* data;
    u_int32_t timestamp;
	int read_errors = 0;

	while (is_running) {
		if (chn.toggle) {
			toggle_video_channel();	// 打开或切换视频
			read_errors = 0;
		}
		else if (rtmp.handle) {
			int ret = srs_rtmp_read_packet(rtmp.handle, &type, &timestamp, &data, &size);
			if (ret != 0) {
				if (ret == 1011/*ERROR_SOCKET_TIMEOUT*/) {
					LOGE("srs_rtmp_read_packet() return ERROR_SOCKET_TIMEOUT\n");
				}
				if (++read_errors > 10) {
					chn.toggle = 1;	// 重新打开视频
				}
				continue;
			}
			
			if (type == SRS_RTMP_TYPE_VIDEO) {
				if (!dec.has_keyframe) {
					// 先获取PPS && SPS
					if (data[0] == 0x17 && data[1] == 0x00) {
						if (!dec.extradata_size) {
							uint8_t* p = dec.extradata, *p1;
							*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 1;

							size = ((((int)data[11]) << 8) | ((int)data[12]));
							memcpy(p, data + 13, size);
							p += size;

							*p++ = 0; *p++ = 0; *p++ = 0; *p++ = 1;

							p1 = (uint8_t*)data + 13 + size + 1;
							size = ((((int)*p1) << 8) | (int)*(p1 + 1));
							memcpy(p, p1 + 2, size);
							
							p += size;
							dec.extradata_size = p - dec.extradata;
							
							if (!create_decoder()) {
								srs_rtmp_destroy(rtmp.handle);
								rtmp.handle = NULL;
								if (chn.on_error_cb)
									chn.on_error_cb(ERR_DECODE_CREATE);
								break;
							}
						}
						dec.has_keyframe = 1;
						dec.nb_frames = 0;
						pthread_mutex_lock(&lock1);
						{
							free_rtmppackets();
						}
						pthread_mutex_unlock(&lock1);
					}
				}
				else if (dec.ctx && data[1] == 0x01) {
					// 缓存接收到的视频帧，如果是关键帧，则删除之前的所有帧再保存;
					// 不是关键帧，如果缓存的第一帧是关键帧，则保存在第二缓存帧，否则替换第一缓存帧。
					pthread_mutex_lock(&lock1);
					{
						if (data[0] == 0x17) {		// 关键帧
							// 删除之前缓存的所有帧
							free_rtmppackets();

							rtmp.packets[0].data = data;
							rtmp.packets[0].size = size;
							rtmp.packets[0].key = 1;
							rtmp.windex = 1;
						}
						else {
							if (rtmp.windex == MAX_PACKETS) {
								if (rtmp.rindex == 0) {
									free_rtmppackets();
								}
								else {
									int i;
									int pos = 0;
									for (i = rtmp.rindex; i < rtmp.windex; i++) {
										rtmp.packets[pos++] = rtmp.packets[i];
									}
									rtmp.rindex = 0;
									rtmp.windex = pos;
									while (pos < MAX_PACKETS)
										rtmp.packets[pos++].data = 0;
								}
							}
							
							rtmp.packets[rtmp.windex].data = data;
							rtmp.packets[rtmp.windex].size = size;
							rtmp.packets[rtmp.windex].key = 0;
							rtmp.windex++;
						}
					}
					pthread_mutex_unlock(&lock1);
					
					continue;
				}
			}

			free(data);
		}
		else {
			usleep(1000 * 50);
		}
	}

	return NULL;
}

void* video_decode_thread(void* p)
{
	struct rtmp_packet packets[MAX_PACKETS];
	int nb_packets, i;
	
	while (is_running) {
		if (!dec.ctx) {
			usleep(1000 * 50);
			continue;
		}
		
		nb_packets = 0;
		
		pthread_mutex_lock(&lock1); 
		{
			if (rtmp.rindex < rtmp.windex) {
				packets[nb_packets++] = rtmp.packets[rtmp.rindex];
				rtmp.packets[rtmp.rindex++].data = 0;
				if (rtmp.windex - rtmp.rindex >= 5) {
					packets[nb_packets++] = rtmp.packets[rtmp.rindex];
					rtmp.packets[rtmp.rindex++].data = 0;
					packets[nb_packets++] = rtmp.packets[rtmp.rindex];
					rtmp.packets[rtmp.rindex++].data = 0;
				}
				else {
					if (rtmp.rindex < rtmp.windex) {
						packets[nb_packets++] = rtmp.packets[rtmp.rindex];
						rtmp.packets[rtmp.rindex++].data = 0;
					}
					if (rtmp.rindex == rtmp.windex) {
						rtmp.rindex = 0;
						rtmp.windex = 0;
					}
				}
			}
		}
		pthread_mutex_unlock(&lock1);
		
		if (nb_packets == 0) {
			usleep(1000 * 50);
			continue;
		}
		
		AVPacket pkt;
		int got_picture = 0, ret = 0;

		if (dec.nb_frames == 0) {
			av_init_packet(&pkt);
			pkt.data = (uint8_t*)dec.extradata;
			pkt.size = dec.extradata_size;
			pkt.pts = dec.pts;
			avcodec_decode_video2(dec.ctx, dec.yuv_frame, &got_picture, &pkt);
			av_packet_unref(&pkt);
		}
//		LOGI("decoding %d packets...", nb_packets);
		for (i = 0; i < nb_packets; i++) {
			// 解码
			av_init_packet(&pkt);
			pkt.data = (uint8_t*)packets[i].data + 5;
			pkt.size = packets[i].size - 5;
			pkt.pts = dec.pts++;
			pkt.data[0] = 0;
			pkt.data[1] = 0;
			pkt.data[2] = 0;
			pkt.data[3] = 1;
			dec.nb_frames++;
			
			ret = avcodec_decode_video2(dec.ctx, dec.yuv_frame, &got_picture, &pkt);
			av_packet_unref(&pkt);
			
			free(packets[i].data);
		}

		if (ret < 0) {
			LOGI("ERROR: decode video error: %d", ret);
		}
		else if (got_picture) {
			uint8_t* frame;
			int notify = 0;
			rotate_yuvframe(dec.render_frame[1], dec.yuv_frame->data[0], dec.yuv_frame->data[1], dec.yuv_frame->data[2]);
//			yuv_rotate_270(render_frame[1], yuv_frame->data[0], yuv_frame->data[1], yuv_frame->data[2], VIDEO_WIDTH, VIDEO_HEIGHT);

			pthread_mutex_lock(&lock2);
			if (!dec.has_render_frame) {
				frame = dec.render_frame[0];
				dec.render_frame[0] = dec.render_frame[1];
				dec.render_frame[1] = frame;
				dec.has_render_frame = 1;
				notify = 1;
			}
			pthread_mutex_unlock(&lock2);

			if (notify && dec.on_frame_cb)
				dec.on_frame_cb();
		}
	}
	return NULL;
}


void videodecode_init()
{
	avcodec_register_all();
	av_log_set_callback(log_callback_report);
	
	memset(&chn, 0, sizeof(chn));
	memset(&rtmp, 0, sizeof(rtmp));
	memset(&dec, 0, sizeof(dec));
	
	pthread_mutex_init(&lock1, 0);
	pthread_mutex_init(&lock2, 0);	
}

void videodecode_uninit()
{
	pthread_mutex_destroy(&lock1);
	pthread_mutex_destroy(&lock2);	
}

void videodecode_start(const char* rtmp_ip, const char* id, int channel, int width, int height, frame_available_cb on_frame_cb, error_cb on_error_cb)
{
	videodecode_stop();
	
	memset(&chn, 0, sizeof(chn));
	memset(&rtmp, 0, sizeof(rtmp));
	memset(&dec, 0, sizeof(dec));
	
	strcpy(chn.rtmp_ip, rtmp_ip);
	strcpy(chn.id, id);
	chn.channel = channel;
	chn.toggle = 1;
	chn.screen_width = width;
	chn.screen_height = height;
	chn.video_height = VIDEO_HEIGHT;
	chn.video_width = (chn.screen_height * VIDEO_HEIGHT / chn.screen_width);
	if (chn.video_width & 0x03)
		chn.video_width = (chn.video_width & 0xfffc) + 4;
	if (chn.video_width == VIDEO_WIDTH)
		chn.video_left = 0;
	else
		chn.video_left = ((VIDEO_WIDTH - chn.video_width) / 3) & 0xfffc;	
	
	dec.on_frame_cb = on_frame_cb;
	chn.on_error_cb = on_error_cb;
	
	is_running = 1;
	pthread_create(&thread, NULL, rtmp_read_thread, NULL);
	pthread_create(&thread_dec, NULL, video_decode_thread, NULL);
}

void videodecode_toggle(int channel)
{
	if (chn.channel != channel) {
		chn.channel = channel;
		chn.toggle = 1;
	}
}

void videodecode_stop()
{
	is_running = 0;

	if (thread) {
		pthread_join(thread, NULL);
		thread = 0;
	}
	if (thread_dec) {
		pthread_join(thread_dec, NULL);
		thread_dec = 0;
	}	

	if (rtmp.handle) {
		srs_rtmp_destroy(rtmp.handle);
		rtmp.handle = 0;
	}
	free_rtmppackets();	

	if (dec.ctx) {
		avcodec_free_context(&dec.ctx);
		dec.ctx = NULL;
	}
	if (dec.yuv_frame) {
		av_frame_free(&dec.yuv_frame);
		dec.yuv_frame = NULL;
	}
	if (dec.render_frame[0]) {
		av_freep(&dec.render_frame[0]);
		dec.render_frame[0] = NULL;
	}
	if (dec.render_frame[1]) {
		av_freep(&dec.render_frame[1]);	
		dec.render_frame[1] = 0;
	}
}

void videodecode_getvideosize(int* width, int* height)
{
	*width = chn.video_width;
	*height = chn.video_height;
}

uint8_t* videodecode_getframe()
{
	uint8_t* frame = NULL;
	
	pthread_mutex_lock(&lock2);
	if (dec.has_render_frame) {
		frame = dec.render_frame[0];
	}
	pthread_mutex_unlock(&lock2);	
	
	return frame;
}

void videodecode_releaseframe()
{
	pthread_mutex_lock(&lock2);
	dec.has_render_frame = 0;
	pthread_mutex_unlock(&lock2);	
}


#ifdef __cplusplus
}
#endif

